#include "str_util.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>

char *str_capitalize(char *dst)
{
    if (dst == NULL)
        return NULL;
    dst[0] = (char)toupper(dst[0]);
    return dst;
}

void test_str_capitalize()
{
    char test[6] = 
        {
            'h', 'e', 'l', 'l', 'o', '\0'
        };
    char *p = str_capitalize(test);
    assert(p[0] == 'H');
}

int str_count(const char *start, const char *end, const char *sub)
{
    size_t length, slength;
    const char *rend, *p;
    int count = 0;
    
    if (start == NULL)
        return count;

    if (end == NULL)
        length = strlen(start);
    else
        length = end - start + 1;
    p = start;
    rend = start + length;
    slength = strlen(sub);

    while (p < rend)
    {
        if (*p == sub[0])
        {
            if ((rend - p) >= slength && strncmp(p, sub, slength) == 0)
            {
                p += slength;
                count++;
                continue;
            }
        }
        p++;
    }
    return count;
}

void test_str_count()
{
    char test[5] = 
        {
            'a', 'a', 'a', 'a', '\0'
        };
    char *sub = "aa";

    assert(str_count_x(test, sub) == 2);
    assert(str_count(test, test+2, sub) == 1);
}

int str_endwith(const char *start, const char *end, const char *suffix)
{
    int existed = 0;
    size_t length, slength;
    const char *pe, *ps;

    if (start == NULL)
        return existed;

    length = strlen(start);
    slength = strlen(suffix);
    if (end == NULL)
        pe = start + length - 1;
    else
        pe = end;
    ps = suffix + slength - 1;

    while (ps >= suffix)
    {
        if (pe < start || *ps != *pe) return existed;
        ps--;
        pe--;
    }
    existed = 1;
    return existed;
}

void test_str_endwith()
{
    char test[6] = 
        {
            'h', 'e', 'l', 'l', 'o', '\0'
        };
    char *sub1 = "llo";
    char *sub2 = "wo";

    assert(str_endwith_x(test, sub1) == 1);
    assert(str_endwith_x(test, sub2) == 0);
    assert(str_endwith(test, test+2, sub2) == 0);
}

int str_startwith(const char *start, const char *end, const char *prefix)
{
    int existed = 0;
    size_t length, slength;
    const char *pe, *pp;

    if (start == NULL)
        return existed;

    length = strlen(start);
    slength = strlen(prefix);
    if (end == NULL)
        pe = start + length - 1;
    else
        pe = end;
    pp = prefix + slength - 1;

    while (prefix <= pp)
    {
        if (start >= pe || *start != *prefix) return existed;
        start++;
        prefix++;
    }
    existed = 1;
    return existed;
}

void test_str_startwith()
{
    char test[6] = 
        {
            'h', 'e', 'l', 'l', 'o', '\0'
        };
    char *sub1 = "hel";
    char *sub2 = "wo";

    assert(str_startwith_x(test, sub1) == 1);
    assert(str_startwith_x(test, sub2) == 0);
    assert(str_startwith(test, test+2, sub1) == 0);
}

int str_find(const char *start, const char *end, const char *sub)
{
    size_t length, slength;
    const char *rend, *ps;
    int lower;

    lower = -1;
    length = strlen(start);
    slength = strlen(sub);
    
    if (start == NULL)
        return lower;

    if (end == NULL)
        rend = start + length - slength;
    else
        rend = end - slength - 1;
    ps = start;

    while (ps <= rend)
    {
        if (strncmp(ps, sub, slength) == 0)
        {
            lower = ps - start;
            break;
        }
        ps++;
    }
    return lower;
}

void test_str_find()
{
    char test[] = 
        {
            'h', 'e', 'l', 'l', 'o', '\0'
        };
    char *sub1 = "hel";
    char *sub2 = "lo";
    char *sub3 = "worldx";

    assert(str_find_x(test, sub1) == 0);
    assert(str_find_x(test, sub2) == 3);
    assert(str_find_x(test, sub3) == -1);
    assert(str_find(test, test+2, sub1) == -1);
}

static
int str_isxxx_helper(const char *src, int (*fn_decide)(int))
{
    int isxxx = 1;

    if (src == NULL || src[0] == '\0')
        return 1 - isxxx;

    while (*src)
    {
        if (!(*fn_decide)(*src))
        {
            isxxx = 0;
            break;
        }
        src++;
    }
    return isxxx;
}

int str_isalnum(const char *src)
{
    return str_isxxx_helper(src, isalnum);
}

void test_str_isalnum()
{
    char *sub1 = "hello123";
    char *sub2 = "123123";
    char *sub3 = "helloworld";
    char *sub4 = "";
    char *sub5 = "hel?12";

    assert(str_isalnum(sub1) == 1);
    assert(str_isalnum(sub2) == 1);
    assert(str_isalnum(sub3) == 1);
    assert(str_isalnum(sub4) == 0);
    assert(str_isalnum(sub5) == 0);
}

int str_isalpha(const char *src)
{
    return str_isxxx_helper(src, isalpha);
}

void test_str_isalpha()
{
    char *sub1 = "hello";
    char *sub2 = "hel123";
    char *sub3 = "";
    char *sub4 = "?123?";

    assert(str_isalpha(sub1) == 1);
    assert(str_isalpha(sub2) == 0);
    assert(str_isalpha(sub3) == 0);
    assert(str_isalpha(sub4) == 0);
}

int str_isdigit(const char *src)
{
    return str_isxxx_helper(src, isdigit);
}

void test_str_isdigit()
{
    char *sub1 = "1234";
    char *sub2 = "hel123";
    char *sub3 = "";
    char *sub4 = "?123?";

    assert(str_isdigit(sub1) == 1);
    assert(str_isdigit(sub2) == 0);
    assert(str_isdigit(sub3) == 0);
    assert(str_isdigit(sub4) == 0);
}

int str_islower(const char *src)
{
    return str_isxxx_helper(src, islower);
}

int str_isspace(const char *src)
{
    return str_isxxx_helper(src, isspace);
}

int str_isupper(const char *src)
{
    return str_isxxx_helper(src, isupper);
}

static
char *str_xxx_helper(char *dst, int (*fn_convert)(int))
{
    if (dst == NULL)
        return NULL;

    char *p = dst;
    while (*p)
    {
        *p = (char)(*fn_convert)(*p);
        p++;
    }
    return dst;
}

char *str_lower(char *dst)
{
    return str_xxx_helper(dst, tolower);
}

void test_str_lower()
{
    char test[] =
        {
            'H', 'E', 'L', 'L', 'O', '\0'
        };
    char *sub1 = "hello";

    assert(strcmp(str_lower(test), sub1) == 0);
}

char *str_upper(char *dst)
{
    return str_xxx_helper(dst, toupper);
}

void test_str_upper()
{
    char test[] =
        {
            'h', 'e', 'l', 'l', 'o', '\0'
        };
    char *sub1 = "HELLO";

    assert(strcmp(str_upper(test), sub1) == 0);
}

char *str_join(const char **table, size_t size, char *dst, size_t offset, const char *join)
{
    if (table == NULL || dst == NULL || join == NULL)
        return NULL;

    size_t idx = 0;
    size_t slength = strlen(join);
    
    strcpy(dst + offset, *table);
    offset += strlen(*table);
    table++;

    for (idx++; idx < size; ++idx)
    {
        strcpy(dst + offset, join);
        offset += slength;
        strcpy(dst + offset, *table);
        offset += strlen(*table);
        table++;
    }
    dst[offset] = '\0';
    
    return dst;
}

void test_str_join()
{
    char **t = (char **)malloc(sizeof(char *) * 5);
    char *p = (char *)malloc(sizeof(char) * 50);
    char *j = ",";
    
    int i;
    for (i = 0; i < 5; ++i)
    {
        *(t + i) = (char *)malloc(sizeof(char) * 2);
        (*(t + i))[0] = (char)('0' + i);
        (*(t + i))[1] = '\0';
    }

    p = str_join((const char **)t, 5, p, 0, j);
    assert(strcmp(p, "0,1,2,3,4") == 0);

    for(i = 0; i < 5; ++i)
    {
        free(*(t + i));
    }
    free(t);
    free(p);
}

char *str_lstrip(char *dst, const char *charset)
{
    static char char_map[256];
    static const char *spaces = " \t\n\f\r\v";
    const char *p = NULL;
    size_t length, sz = 0;

    if (dst == NULL)
        return NULL;

    length = strlen(dst);
    memset(char_map, 0, sizeof(char) * 256);
    if (charset == NULL)
        p = spaces;
    else
        p = charset;
    
    while (*p)
    {
        char_map[(unsigned char)*p] = 1;
        p++;
    }

    p = dst;
    while (*p)
    {
        if (char_map[(unsigned char)*p] ==0)
            break;
        p++;
        sz++;
    }
    strcpy(dst, p);
    dst[length - sz] = '\0';
    
    return dst;
}

void test_str_lstrip()
{
    char test[] = 
        {
            ' ', ' ', '\t', 'h', 'e', 'l', 'l', 'o', '\0'
        };
    char *p = NULL;
    char *charset = " \thel";

    p = str_lstrip_x(test);
    assert(strcmp(p, "hello") == 0);
    p = str_lstrip(test, charset);
    assert(strcmp(p, "o") == 0);
}

char *str_replace(char *dst, const char *src, const char *olds, const char *news, int count)
{
    size_t length = strlen(src);
    size_t olds_len = strlen(olds);
    size_t news_len = strlen(news);
    size_t offset = 0;
    const char *pprev = src;
    const char *pnext = src;
    const char *rend = (src + length - olds_len);
    int rcount = 0;
    
    while (pnext <= rend)
    {
        if (count != -1 && rcount >= count)
            break;
        
        if (strncmp(pnext, olds, olds_len) == 0)
        {
            strncpy(dst + offset, pprev, pnext - pprev);
            offset += pnext - pprev;
            strncpy(dst + offset, news, news_len);
            offset += news_len;
            pnext +=olds_len;
            pprev = pnext;
            rcount++;
        }
        else
        {
            pnext++;
        }
    }
    if (pnext != (src + length))
    {
        pnext = src + length;
        strncpy(dst + offset, pprev, pnext - pprev);
        offset += pnext - pprev;
    }
    dst[offset] = '\0';

    return dst;
}

void test_str_replace()
{
    char *dst = malloc(sizeof(char) * 50);
    char *src = "abcdabg";
    char *p = NULL;

    p = str_replace_x(dst, src, "ab", "xx");
    assert(strcmp(p, "xxcdxxg") == 0);
    p = str_replace(dst, src, "ab", "xx", 1);
    assert(strcmp(p, "xxcdabg") == 0);
    p = str_replace(dst, src, "ab", "xx", 0);
    assert(strcmp(p, "abcdabg") == 0);
}

int str_rfind(const char *start, const char *end, const char *sub)
{
    size_t length, slength;
    const char *lstart, *pe;
    int high;

    high = -1;
    length = strlen(start);
    slength = strlen(sub);
    
    if (start == NULL)
        return high;

    if (end == NULL)
        pe = start + length - 1;
    else
        pe = end;
    pe = pe - slength + 1;
    lstart = start;

    while (pe >= lstart)
    {
        if (strncmp(pe, sub, slength) == 0)
        {
            high = pe - lstart;
            break;
        }
        pe--;
    }

    return high;
}

void test_str_rfind()
{
    char test[] = 
        {
            'h', 'e', 'l', 'l', 'o', '\0'
        };
    char *sub1 = "hel";
    char *sub2 = "lo";
    char *sub3 = "worldx";

    assert(str_rfind_x(test, sub1) == 0);
    assert(str_rfind_x(test, sub2) == 3);
    assert(str_rfind_x(test, sub3) == -1);
    assert(str_rfind(test, test+1, sub1) == -1);
}

char *str_rstrip(char *dst, const char *charset)
{
    static char char_map[256];
    static const char *spaces = " \t\n\f\r\v";
    const char *pp = NULL;
    char *p = NULL;
    size_t length;

    if (dst == NULL)
        return NULL;

    length = strlen(dst);
    memset(char_map, 0, sizeof(char) * 256);
    if (charset == NULL)
        pp = spaces;
    else
        pp = charset;
    
    while (*pp)
    {
        char_map[(unsigned char)*pp] = 1;
        pp++;
    }

    p = dst + length - 1;
    while (p >= dst)
    {
        if (char_map[(unsigned char)*p] == 0)
            break;
        *p = '\0';
        p--;
    }
    return dst;
}

void test_str_rstrip()
{
    char test[] = 
        {
            'h', 'e', 'l', 'l', 'o', '\t', ' ', ' ', '\0'
        };
    char *p = NULL;
    char *charset = " \tlo";

    p = str_rstrip_x(test);
    assert(strcmp(p, "hello") == 0);
    p = str_rstrip(test, charset);
    assert(strcmp(p, "he") == 0);
}

char **str_split(char **table, size_t *size, const char *src, const char *separator, const int maxsplit)
{
    size_t length, slength;
    int idx, icount;
    const char *pprev = src;
    const char *pnext = src;

    length = strlen(src);
    slength = strlen(separator);
    *size = 0;
    icount = 0;

    while ((idx = str_find_x(pnext, separator)) != -1)
    {
        if (maxsplit != -1 && icount >= maxsplit)
            break;
        
        pnext = pnext + idx;
        if (pnext != pprev)
        {
            strncpy(*(table + *size), pprev, pnext - pprev);
            (*(table + *size))[pnext - pprev] = '\0';
            *size = *size + 1;
            icount++;
        }
        pnext += slength;
        pprev = pnext;
    }

    if (maxsplit != -1 && icount >= maxsplit)
        goto finish;
    
    pnext = src + length;
    if (pnext != pprev)
    {
        strncpy(*(table + *size), pprev, pnext - pprev);
        (*(table + *size))[pnext - pprev] = '\0';
        *size = *size + 1;
    }

 finish:
    return table;
}

void test_str_split()
{
    const char *src = "hello,world,mans";
    const char *sep = ",";
    const int maxsplit = 2;
    int i;
    size_t sz;
    
    char **table = (char **)malloc(sizeof(char *) * 3);
    for (i = 0; i < 3; ++i)
    {
        *(table + i) = (char *)malloc(sizeof(char) * 10);
    }

    table = str_split_x(table, &sz, src, sep);
    for (i = 0; i < 3; ++i)
        printf("%s\n", *(table + i));

    table = str_split(table, &sz, src, sep, maxsplit);
    for (i = 0; i < maxsplit; ++i)
        printf("%s\n", *(table + i));
}

char *str_strip(char *dst, const char *charset)
{
    dst = str_lstrip(dst, charset);
    dst = str_rstrip(dst, charset);
    return dst;
}

void test_str_strip()
{
    char test[] = 
        {
            ' ', ' ', 'h', 'e', 'l', 'l', 'o', '\t', ' ', ' ', '\0'
        };
    char *p = NULL;
    char *charset = " \thlo";

    p = str_strip_x(test);
    assert(strcmp(p, "hello") == 0);
    p = str_strip(test, charset);
    assert(strcmp(p, "e") == 0);
}

char *str_swapcase(char *dst)
{
    if (dst == NULL)
        return NULL;

    char *p = dst;
    while (*p)
    {
        if (islower(*p))
            *p = (char)toupper(*p);
        else if (isupper(*p))
            *p = (char)tolower(*p);
        p++;
    }
    return dst;
}

void test_str_swapcase()
{
    char test[] = 
        {
            'h', 'E', 'l', 'l', 'O','\0'
        };
    char *p = NULL;

    p = str_swapcase(test);
    assert(strcmp(p, "HeLLo") == 0);
}

char *str_translate(char *dst, const char *src, const char *from, const char *to, const char *deletechars)
{
    static char delete_chars_set[256];
    static int from_to_map[256];
    const char *p;
    int tflag, dflag, idx;

    tflag = dflag = 1;
    idx = 0;
    p = from;
    
    if (from == NULL && to == NULL)
        tflag = 0;
    if (deletechars == NULL)
        dflag = 0;

    if (tflag)
    {
        memset(from_to_map, -1, sizeof(int) * 256);
        p = from;
        while (*p)
        {
            from_to_map[(unsigned char)*p] = (unsigned char)to[idx++];
            p++;
        }
    }
    
    if (dflag)
    {
        memset(delete_chars_set, 0, sizeof(char) * 256);
        p = deletechars;
        while (*p)
        {
            delete_chars_set[(unsigned char)*p] = 1;
            p++;
        }
    }

    p = src;
    idx = 0;
    while (*p)
    {
        if (dflag)
        {
            if (delete_chars_set[(unsigned char)*p] == 1)
            {
                p++;
                continue;
            }
        }

        if (tflag)
        {
            if (from_to_map[(unsigned char)*p] != -1)
            {
                dst[idx++] = (char)from_to_map[(unsigned char)*p];
            }
            else
            {
                dst[idx++] = *p;
            }
        }
        else
        {
            dst[idx++] = *p;
        }
        p++;
    }
    dst[idx] = '\0';
    
    return dst;
}

void test_str_translate()
{
    char test[] = 
        {
            'h', 'E', 'l', 'l', 'O', ' ', ' ', '\0'
        };
    char *p = (char *)malloc(sizeof(char) * 10);
    const char *deletechars = " ";
    const char *from = "ll";
    const char *to = "KK";

    p = str_translate_delete(p, test, deletechars);
    assert(strcmp(p, "hEllO") == 0);
    p = str_translate(p, test, from, to, deletechars);
    assert(strcmp(p, "hEKKO") == 0);
}


#ifdef TEST_STR
int main(int argc, char *argv[])
{
    test_str_capitalize();
    test_str_count();
    test_str_endwith();
    test_str_startwith();
    test_str_find();
    test_str_isalnum();
    test_str_isalpha();
    test_str_isdigit();
    test_str_lower();
    test_str_upper();
    test_str_join();
    test_str_lstrip();
    test_str_replace();
    test_str_rfind();
    test_str_rstrip();
    test_str_split();
    test_str_strip();
    test_str_swapcase();
    test_str_translate();
    
    return 0;
}
#endif
    
